Eigene Prüfungen implementieren
- Grundlagen
- Testprogramm
- Eigener Hierarchieknoten
- Eigene Prüfungen implementieren
- Attribute
- Dokumentation
- Fehlermeldungen
Nun wollen wir eine eigene Prüfung in den Code Inspector einbauen. Hierfür müssen wir eine der beiden Klasse CL_CI_TEST_ROOT_TEMPLATE oder CL_CI_TEST_SCAN_TEMPLATE kopieren und entsprechend ausprogrammieren.
Wozu eigentlich eigene Prüfungen?
Vielleicht fragen Sie sich die ganze Zeit, wozu Sie überhaupt eigene Prüfungen benötigen sollten, da ja bereits viele wichtige Checks im Standard vorhanden sind…
Erstens ist es immer gut zu wissen, dass es überhaupt geht. Zweitens gibt es eine Vielzahl von Prüfungen, die man vornehmen könnte:
- Verweist die Änderung im Programmkopf auf eine gültige Ticket-Nummer?
- Wurde beim Update bestimmter kundeneigener Tabellen darauf geachtet auch eine zweite zwingend notwendige Tabelle anzupassen?
- Wurde ein Update auf eine Tabelle vorgenommen, für die nur ein bestimmter Funktionsbaustein verwendet werden soll?
- Wurde eine Tabelle gelesen, die besser mit einem Methode gelesen werden sollte, da bei dieser Tabelle in der Regel immer auch Daten nachgelesen werden müssen, deren Beschaffung sehr performance-kritisch ist?
- Wurde eine Programmänderung in einem anderen externen Tool ebenfalls dokumentiert?
- Wird eine Standard-SAP-Klasse verwendet, für die es jedoch eine kundeneigene vererbte Klasse gibt, die wichtige Sonderfunktionen enthält?
Die Liste ließe sich noch weiter führen. Jedes Unternehmen hat spezifische Anforderungen, deren Einhaltung durch automatische Tests gut geprüft werden kann.
Aufgabe
Kommen wir nun zu des Pudels Kern oder wie das so schön heißt: Wir legen eine eigene Prüfung an. Da der Code Inspector ja helfen soll, die Programmqualität zu verbessern und den Anwender auf Fehler hinzuweisen, wollen wir eine Prüfung implementieren, die den Programmierer
- auf nicht zu verwendende BAPIs (da veraltet) hinweist und
- überprüft, ob nach einem BAPI auch ein BAPI_TRANSACTION_COMMIT erfolgt.
Erstere Prüfung kann wichtig sein, da bestimmte Funktionen in einigen BAPIs vielleicht nicht funktionieren oder diese BAPIs nicht mehr gewartet werden.
Zweitere Prüfung soll helfen, einen weit verbreiteten Anfängerfehler zu vermeiden: Das fehlende explizite COMMIT bei einem BAPI, der Änderungen ausführt.
Klasse anlegen
Als erstes kopieren wir die Klasse CL_CI_TEST_SCAN_TEMPLATE auf Z_CL_CI_TEST_SCAN_TT1.
Diese Klasse erbt von CL_CI_TEST_SCAN. Die folgenden drei Methoden sind bereits vorhanden und müssen angepasst (redefiniert) werden:
Methode CONSTRUCTOR
Zuerst passen wir den Konstruktor an. Hier wird definiert, wie der Test heißt, und welcher Kategorie er zugeordnet ist:
super->constructor( ).
description = ‘Tricktresor BAPI’. “required
category = ‘Z_CL_CI_CATEGORY_TT’. “required
version = ‘000’. “required
has_attributes = ‘ ‘. “optional
attributes_ok = ‘ ‘. “optional
myname = ‘Z_CL_CI_TEST_SCAN_TT1’. “own name
Nachdem die Klasse aktiviert wurde und in Transaktion SCI in der Test-Verwaltung aktiviert wurde, ist die neue Prüfung bereits in der Prüfvariante sichtbar:
Damit die Prüfung allerdings auch funktioniert, müssen wir natürlich noch etwas mehr unternehmen…
Methode RUN
Hier erfolgt die eigentlich Prüfung anhand des Quelltextes. Es stehen einerseits der Quelltext als auch die Aufsplittung des Quelltextes in TOKENS zur Verfügung.
Sollte der Check der allererste in der Liste der auszuführenden Prüfungen sein, so muss erst einmal die Referenz auf das Prüfobjekt geholt werden:
IF ref_scan IS INITIAL.
CHECK get( ) = ‘X’.
ENDIF.
Nun nehmen wir den Quellcode in die Mangel und suchen veraltete BAPIs… Zuerst die Datendeklaration:
*** DATA
FIELD-SYMBOLS <token> TYPE stokesx.
DATA ls_token1 TYPE stokesx.
DATA ls_token2 TYPE stokesx.
DATA lv_tabix1 TYPE i.
DATA lv_tabix2 TYPE i.
DATA lv_level TYPE i.
DATA lv_include TYPE program.
DATA l_errcnt TYPE sci_errcnt.
DATA lv_objname TYPE c LENGTH 50.
DATA lv_bapi_used TYPE c.
DATA lv_bapi_commit TYPE c.
Beschreibung
Nun kommt das Coding, in dem wir zuerst nach dem Vorkommen von “CALL FUNCTION” und dann nach dem verwendeten Funktionsbaustein suchen. Wenn es sich bei dem Funktionsbaustein um enen BAPI handelt (Funktionsbaustein beginnt mit “BAPI”), dann prüfen wir, ob der Baustein irgend etwas mit Datenänderungen zu tun hat. Das tun wir ganz trivial indem wir den Namen nach “CREATE” oder “CHANGE” durchsuchen.
Desweiteren merken wir uns die Verwendung des Funktionsbausteins “BAPI_TRANSACTION_COMMIT” mit dem Änderungen mit BAPIs zwingend bestätigt werden müssen.
Eine direkte Programmfolge von BAPI und COMMIT prüfen wir nicht, da sie zu umfangreich wäre. Unsere Prüfung würde also nicht erkennen, dass nach BAPI_CHANGE_1 zwar einen COMMIT abgesetzt wird, aber nach BAPI_CREATE_2 keiner. Wird prüfen nur, ob überhaupt ein BAPI_TRANSACTION_COMMIT im Programm vorhanden ist.
ANMERKUNG:
Die Prüfungen erfolgen hier nun explizit auf einen BAPI, der im Programm hart codiert ist. Im “wahren Leben” wäre eine Tabelle, in der die zu prüfenden BAPIs aufgeführt werden, sicherlich sinnvoller.
Coding
*** seach for “CALLS”
LOOP AT ref_scan->tokens ASSIGNING <token>
WHERE type = ‘I’
AND str = ‘CALL’.
*** Check next token
lv_tabix1 = sy-tabix + 1.
READ TABLE ref_scan->tokens INTO ls_token1 INDEX lv_tabix1.
IF sy-subrc = 0 AND ls_token1-str = ‘FUNCTION’.
*** Command is CALL FUNCTION…
lv_tabix2 = sy-tabix + 1.
READ TABLE ref_scan->tokens INTO ls_token2 INDEX lv_tabix2.
IF sy-subrc = 0.
*** Get call level and current include
LOOP AT ref_scan->statements INTO statement_wa
WHERE from <= ls_token2-row AND to >= ls_token2-row.
lv_level = statement_wa-level.
ENDLOOP.
lv_include = get_include( p_level = lv_level ).
*** transform name of function module as it is stored with
*** leading and ending inverted comma
lv_objname = ls_token2-str.
TRANSLATE lv_objname USING ”’ ‘.
SHIFT lv_objname LEFT DELETING LEADING space.
*** called BAPI?
IF lv_objname(4) = ‘BAPI’.
*** Does the BAPI make changes?
IF lv_objname CS ‘CREATE’ OR
lv_objname CS ‘CHANGE’.
*** => yes: (=> Commit needed!!)
lv_bapi_used = ‘X’.
ENDIF.
*** Check for several BAPIS
CASE lv_objname.
WHEN ‘BAPI_SALESORDER_CREATEFROMDATA’.
*** send message
inform( p_sub_obj_type = ‘PROG’
p_sub_obj_name = lv_include
p_line = ls_token2-row
p_column = ls_token2-col
p_errcnt = l_errcnt
p_kind = c_warning
p_test = myname
p_code = ‘0001’
p_param_1 = ls_token2-str
p_param_2 = space
p_param_3 = space
p_param_4 = space ).
WHEN ‘BAPI_TRANSACTION_COMMIT’.
*** Commit coded
lv_bapi_commit = ‘X’.
ENDCASE. “BAPI name
ENDIF. “BAPI?
ENDIF. “name of function module found
ENDIF. “Call Function found
ENDLOOP. “token “CALL”
*** If a BAPI is used that creates or changes an
*** object but no Commit-Statement found: Inform user
IF lv_bapi_used <> space AND lv_bapi_commit = space.
inform( p_sub_obj_type = ‘PROG’
p_sub_obj_name = trdir-name
p_line = 1
p_column = 1
p_errcnt = l_errcnt
p_kind = c_warning
p_test = myname
p_code = ‘0002’
p_param_1 = ‘BAPI_TRANSACTION_COMMIT’
p_param_2 = space
p_param_3 = space
p_param_4 = space ).
ENDIF.
Auf die Verwendung der Methode “INFORM” gehen wir im nächsten Artikel “Fehlermeldungen” näher ein.
INFO:
In der Variablen REF_INCLUDE steht der aktuelle Quellcode!
Methode GET_MESSAGE_TEXT
Auf diese Methode gehen wir auf der nächsten Seite “Fehlermeldungen” genauer ein.
- Interview mit Björn Schulz (Software-Heroes.com) - 3. September 2024
- Daten aus ALV ermitteln - 3. September 2024
- So lange es den SAPGUI noch gibt… - 27. Juni 2024